#ifndef OPENGLPAINTINGAREA_H
#define OPENGLPAINTINGAREA_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Compatibility>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QtMath>

#include "ctl_types.hpp"
#include "rb_types.hpp"
#include "game.h"

const int canvas_width = 300;
const int canvas_height = 720;
const int board_width = 10;
const int board_height = 23;
const int block_width = canvas_width / board_width; //30
const int block_height = canvas_height / (board_height + 1); //30
const unsigned int fixed_speed_slot = 500;

class element : protected QOpenGLFunctions_3_3_Compatibility {
public:
    GLfloat * verts;
    GLint v_size;
    GLushort * indices;
    GLint i_size;
    GLfloat rgb[3];
    GLuint  VAO;            // 顶点数组对象：Vertex Array Object，VAO
    GLuint  VBO;           // 顶点缓冲对象：Vertex Buffer Object，VBO
    GLuint  EBO;           // 索引缓冲对象：Element Buffer Object，EBO或Index Buffer Object，IBO
    GLboolean hasVAO;      // 判断是否需要生成VAO
    element() {
        rgb[0] = 1.0f;
        rgb[1] = 1.0f;
        rgb[2] = 1.0f;
        verts = new GLfloat[12]; // default empty
        v_size = 12 * sizeof(GLfloat);
        indices = new GLushort[4];
        i_size = 4 * sizeof(GLushort);
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;
        indices[3] = 3;
        hasVAO = false;
        setVert(rb_types::Point(-1, -1));
    }
    element(const rb_types::Point p) {
        rgb[0] = 1.0f;
        rgb[1] = 1.0f;
        rgb[2] = 1.0f;
        verts = new GLfloat[12]; // default empty
        v_size = 12 * sizeof(GLfloat);
        indices = new GLushort[4];
        i_size = 4 * sizeof(GLushort);
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;
        indices[3] = 3;
        hasVAO = false;
        setVert(p);
    }
    ~element() {
        assert(verts != NULL);
        delete verts;
        assert(indices != NULL);
        delete indices;
    }
    element(const element& e) {
        rgb[0] = e.rgb[0];
        rgb[1] = e.rgb[1];
        rgb[2] = e.rgb[2];
        verts = new GLfloat[12]; // default empty
        v_size = 12 * sizeof(GLfloat);
        indices = new GLushort[4];
        i_size = 4 * sizeof(GLushort);
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;
        indices[3] = 3;
        hasVAO = false;
        for (int i = 0; i < 12; ++i) verts[i] = e.verts[i];
    }
    bool operator ==(const element& e) {
        return verts == e.verts && indices == e.indices;
    }
    bool operator !=(const element& e) {
        return !(*this == e);
    }
    void operator =(const element& e) {
        assert(*this != e);
        rgb[0] = e.rgb[0];
        rgb[1] = e.rgb[1];
        rgb[2] = e.rgb[2];
        verts = new GLfloat[12]; // default empty
        v_size = 12 * sizeof(GLfloat);
        indices = new GLushort[4];
        i_size = 4 * sizeof(GLushort);
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;
        indices[3] = 3;
        hasVAO = false;
        for (int i = 0; i < 12; ++i) verts[i] = e.verts[i];
    }

    void setup() {
        initializeOpenGLFunctions();

        // 初始化各种对象
        if (!hasVAO) {
            glGenVertexArrays(1, &VAO);
            glGenBuffers(1, &VBO);
            glGenBuffers(1, &EBO);
            hasVAO = true;
        }

        glBindVertexArray(VAO);             // 绑定顶点数据
        glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定顶点缓冲对象
        glBufferData(GL_ARRAY_BUFFER, v_size, verts, GL_STATIC_DRAW);
                                                    //  GL_STATIC_DRAW  ：数据不会或几乎不会改变
                                                    //  GL_DYNAMIC_DRAW ：数据会被改变很多
                                                    //  GL_STREAM_DRAW  ：数据每次绘制时都会改变
        // 链接顶点属性
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float)*3, 0);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);  // 绑定索引缓冲对象
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, i_size, indices, GL_STATIC_DRAW);
        glBindVertexArray(0);                        // 解绑顶点数据
    }
    void draw(const GLuint shaderProgramID) {
        initializeOpenGLFunctions();

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glBindVertexArray(VAO);  // 绑定顶点数据，绘制图元
        glUniform3fv(glGetUniformLocation(shaderProgramID, "orgColor"), 1, rgb);
        glDrawElements(GL_POLYGON, i_size/sizeof(GLushort), GL_UNSIGNED_SHORT, NULL);
        glBindVertexArray(0);               // 解绑顶点数据
    }
    void setVert(const rb_types::Point p) {
        verts[0] = p.x * block_width + 1.0f;
        verts[1] = p.y * block_height + 1.0f;
        verts[2] = 0.0f;
        verts[3] = (p.x + 1) * block_width - 1.0f;
        verts[4] = p.y * block_height + 1.0f;
        verts[5] = 0.0f;
        verts[6] = (p.x + 1) * block_width - 1.0f;
        verts[7] = (p.y + 1) * block_height - 1.0f;
        verts[8] = 0.0f;
        verts[9] = p.x * block_width + 1.0f;
        verts[10] = (p.y + 1) * block_height - 1.0f;
        verts[11] = 0.0f;
    }
};

class Canvas : protected QOpenGLFunctions_3_3_Compatibility {
    friend class element;
    friend class RussianBlock;
public:
    Canvas() {
        for (int i = 0; i < board_height; ++i)
            boardIndex.push_back(std::vector<bool>(board_width, false));

        //for (int i = 0; i < board_width; ++i)
        //    for (int j = 0; j < board_height; ++j)
        //        qDebug() << boardIndex[i][j];

        generateCanvas();
    }
    Canvas(const Canvas& ca) {
        boardIndex = ca.boardIndex;
        generateCanvas();
    }

    void draw(GLuint shaderProgramID) {
        initializeOpenGLFunctions();
        //for (auto v : boardCanvas) {
        //    v.setup();
        //    v.draw(shaderProgramID);
        //}
        ///换一种新的方式：boardCanvas固定不变，boardIndex成为渲染与否的标志
        for (int i = 0; i < board_height; ++i) {
            for (int j = 0; j < board_width; ++j) {
                if (boardIndex[i][j]) {
                    assert(i * board_width + j < boardCanvas.size());
                    boardCanvas.at(i * board_width + j).setup();
                    boardCanvas.at(i * board_width + j).draw(shaderProgramID);
                }
            }
        }
    }
    void drawUpside(GLuint shaderProgramID) {
        initializeOpenGLFunctions();
        /// draw next block
        for (int i = 0; i < 4; ++i) {
            nextBlockCanvas[i].setup();
            nextBlockCanvas[i].draw(shaderProgramID);
        }
    }

    void clear() {
        std::vector<std::vector<bool> >().swap(boardIndex); //boardIndex.clear(); // may be cause of memory leak
        for (int i = 0; i < board_height; ++i)
            boardIndex.push_back(std::vector<bool>(board_width, false));
    }

private:
    inline void generateCanvas() {
        std::vector<element>().swap(boardCanvas); // boardCanvas.clear();
        for (int i = 0; i < board_height; ++i) {
            for (int j = 0; j < board_width; ++j) {
                boardCanvas.push_back(element(rb_types::Point(j, i)));
            }
        }
    }
    std::vector<std::vector<bool> > boardIndex; // board_width * board_height
    std::vector<element> boardCanvas; // only render the true block location
    element nextBlockCanvas[4]; // a block is always 4 square-sub-blocks
};

class OpenGLPaintingArea : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Compatibility
{
    friend class Canvas;
public:
    OpenGLPaintingArea(QWidget *parent);
    virtual ~OpenGLPaintingArea();

    void installShader();
    Canvas* getCanvas();
    void setGameState(const ctl_types::gaming_tag& gt);
    ctl_types::gaming_tag getGameState() const;

protected:
    void initializeGL();
    void paintGL();
    void resizeGL(int w,int h);

private:
    GLuint shaderProgramID;
    ctl_types::gaming_tag gameState;
    Canvas* canvas;
};

#endif // OPENGLPAINTINGAREA_H
